Async / Await 是 ECMAScript 8 ( ES2017 ) 引入的一個功能,它是建立在 Promise 之上的語法糖,允許我們使用 async
關鍵字標記一個函式,表示這個函式是非同步的,使其返回一個 Promise。在標記為 async 的函式內部,我們可以使用 await
關鍵字等待 Promise 物件的取得或失敗,程式碼將等待非同步操作完成後,才會繼續執行下一行程式碼。這使得我們能夠 以同步的方式來處理非同步操作
,避免了回呼地獄,同時保持了程式的性能優勢。
我們使用 Async / Await 繼續改寫 上一章 波動拳 code 的範例:
const asyncUser = (id: number): Promise<string> => {
return new Promise((resolve, _) => {
setTimeout(() => {
resolve(`我是第 ${id} 個`);
}, 1000);
});
};
const processData = async () => {
const res1 = await asyncUser(1);
console.log(res1); // 輸出: 我是第 1 個
const res2 = await asyncUser(2);
console.log(res2); // 輸出: 我是第 2 個
const res3 = await asyncUser(3);
console.log(res3); // 輸出: 我是第 3 個
const res4 = await asyncUser(4);
console.log(res4); // 輸出: 我是第 4 個
const res5 = await asyncUser(5);
console.log(res5); // 輸出: 我是第 5 個
};
processData();
在這個範例中,我們定義了一個 asyncUser 函式,用於模擬非同步操作的延遲。然後,我們定義了一個 processData 函式,使用 async 關鍵字標記,內部使用了 await 關鍵字等待 asyncUser 函式的執行,最後將會依序每秒輸出。這使得我們能夠以同步的風格來描述非同步操作的順序,程式碼的執行過程更加清晰可讀。
還記得 Promise 是使用 .then() 和 .catch() 的方式來處理成功和失敗吧,而 Async / Await 則是使用 try/catch
的方式來捕捉非同步操作中的錯誤,並可以適時地使用 throw
來拋出錯誤以進行排除故障。
try 區塊: 在這個區塊中,我們會放置成功或判斷可能引發異常的程式碼,如果錯誤發生,會立即跳轉到 catch 區塊。
catch 區塊: 在這個區塊中,我們可以處理捕捉到的錯誤,並根據情況進行適當的調整。
所以當一個 Promise 失敗時 ( rejected ),await 會抛出一個錯誤異常,我們可以在 catch 區塊中捕獲這個錯誤。
以下是一個包含錯誤處理的範例:
interface IData {
userId: number;
id: number;
title: string;
body: string;
}
async function fetchRemoteData(): Promise<IData> {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
// promise 失敗則拋出異常
if (!res.ok) throw new Error("取得資料失敗!");
const data: IData = await res.json();
return data;
} catch (error) {
throw error;
}
}
async function fetchData() {
try {
const remoteData = await fetchRemoteData();
console.log(remoteData);
} catch (error) {
console.error(error);
}
}
fetchData();
在這個範例中,我們使用了 fetchData 函式來獲取數據,並使用 await 等待數據的返回。如果獲取過程中出現錯誤,await 將會拋出一個異常,我們可以在 catch 區塊中捕獲這個異常並進行錯誤處理。
不過如果我們使用的是 error.message 則會發生
error 的類型為 unknown
的錯誤。
有以下幾種解法:
strict 改為 false
。
// tsconfig.json
{
"compilerOptions": {
// ... ,
"strict": false
// ...,
}
}
useUnknownInCatchVariables 改為 false
。
// tsconfig.json
{
"compilerOptions": {
// ... ,
"strict": true,
"useUnknownInCatchVariables": false
// ...,
}
}
斷言為 Error
的方式:
(async () => {
try {
// ...
} catch (error) {
console.error((error as Error).message);
// 或是寫成 console.error((<Error>error).message);
}
})();
instanceof
的方式:
(async () => {
try {
// ...
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
}
})();
any
(async () => {
try {
// ...
} catch (error: any) {
console.error(error.message);
}
})();
以上幾種寫法都可以消除 error 的類型為 unknown 的錯誤,可以看個人、團隊或專案任選一種即可。
昨天我們有提到使用 Promise all 來同時處理多個非同步操作,等待它們全部完成後再繼續執行。今天我們加上 Async / Await 來處理這種情況。
看以下範例:
interface IData {
userId: number;
id: number;
title: string;
body: string;
}
const fetchRemoteData = async (): Promise<IData[]> => {
const promiseAllData = [
fetch("https://jsonplaceholder.typicode.com/posts/1"),
fetch("https://jsonplaceholder.typicode.com/posts/2"),
fetch("https://jsonplaceholder.typicode.com/posts/3"),
];
try {
const res = await Promise.all(promiseAllData);
// 確保全部都成功取得資料
res.forEach((item) => {
if (!item.ok) throw new Error("取得資料失敗!");
});
const data: IData[] = await Promise.all(res.map((item) => item.json()));
return data;
} catch (error) {
throw error;
}
};
const fetchData = async () => {
try {
const remoteData = await fetchRemoteData();
console.log(remoteData);
} catch (error) {
console.error((error as Error).message);
}
};
fetchData();
上面的範例,我們同時發起多個數據請求,然後使用 Promise.all
等待所有的 Promise 結束,這樣我們就可以確保所有的非同步操作都完成後,再一次性處理它們的結果。
使用 Async / Await 可以使我們以更直觀、更具同步風格的方式來處理非同步操作,讓程式碼更具可讀性和可維護性,還能夠保持非同步操作的性能優勢。
Promise
適合於處理單一非同步操作
。.then() 和 .catch() 的鏈式操作
,讓我們可以輕鬆地處理多個非同步操作。Async / Await
適合用於較複雜的非同步流程
,如多個非同步操作之間的協調。使非同步代碼更具可讀性
,因為它看起來像同步代碼,易於理解。try/catch
可以更容易地捕捉和處理錯誤。程式碼更加簡潔
。Promise | Async / Await | |
---|---|---|
簡單性 | ✔︎ | |
簡潔性 | ✔︎ | |
維護性 | ✔︎ | |
可讀性 | ✔︎ | ✔︎ |
鏈式操作 | ✔︎ | ✔︎ |
錯誤處理 | .then()、.catch() | try / catch |
適用範圍 | 較簡單的非同步流程 | 較複雜的非同步流程 |
JavaScript 版本 | ECMAScript 2015 ( ES6 ) | ECMAScript 2017 ( ES8 ) |
所以不管是選擇使用 Promise 還是 Async / Await ,還是取決於程式碼結構和專案需求。對於簡單的非同步操作,Promise 可能更適合,而對於較複雜的非同步操作,特別是需要錯誤處理和協調的情況,Async / Await 可能會是更好的方式。